#include "scene.h"
#include "defines/rayState.h"
#include "combinedObject.h"
#include <cmath>
#include <GL/gl.h>
#include <iostream>


Scene::Scene(){
    idObjects = 0;
    depth = 3;
    camera = new Camera();
    light = new Light();
}

Scene::~Scene(){
    unsigned int size = objects.size();
    for(unsigned int i = 0; i < size; i++){
        Object *obj = objects.back();
        objects.pop_back();
        delete obj;
    }
    delete camera;
    delete light;
}

void Scene::addObject(Object *_o){
    objects.push_back(_o);
}

Object *Scene::getObject(unsigned int _i) const{
    return objects[_i];
}

unsigned int Scene::getObjectsSize() const{
    return objects.size();
}

void Scene::deleteObject(Object *_o){
    int size = objects.size();
    for (int i = size; i > 0; i--){
        if ( objects.at(i - 1) == _o ){
            objects.erase(objects.begin() + i - 1);
        }
    }
}

int Scene::nextObjectsID(){
    return idObjects++;
}

void Scene::setNextObjectsID(int _id){
    idObjects = _id + 1;
}

void Scene::draw(bool _showWireframeObjects, bool _showCamera, bool _showRayTest){
    if(_showCamera) camera->draw();
    Object *obj;
    for(unsigned int i = 0; i < objects.size(); i++){
        obj = objects.at(i);
        obj->draw(_showWireframeObjects);
    }

    if(_showRayTest){
        drawRay();
    }
}

void Scene::drawRay(){
//
//    Vector3D directionToLight = (light->getPosition() - nearestPoint).normalize();
//
//    double ln = nearestNormal.dotProduct(directionToLight);
//    Vector3D reflected = (_ray.getDirection() + (nearestNormal * (-2.0 * ln))).normalize();
//    Ray reflectionRay(nearestPoint + (reflected*0.01), reflected);

    if(!listOfPoints.empty()) {
        glPointSize(4);
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHTING);

        Vector3D p1;
        Vector3D n1;
        Vector3D p2;
        unsigned int type;
        Trio trio;

        bool firstTime = true;
        for(unsigned int i = 0; i < listOfPoints.size() - 1; i++){
            glLineWidth(3);
            trio = listOfPoints.at(i);
            p1 = trio.point;
            n1 = trio.normal;
            type = trio.type;
            p2 = listOfPoints.at(i + 1).point;

            if(!firstTime){
                glColor3f(1.0, 1.0, 0.0);
                glBegin(GL_POINTS);
                    glVertex3dv(p1.intoVector());
                glEnd();
            }

            if(type == IN) glColor3f(1.0, 0.0, 0.0);
            else if (type == ON) glColor3f(1.0, 1.0, 0.0);
            else if (type == OUT) glColor3f(0.0, 1.0, 0.0);
            glBegin(GL_LINES);
                glVertex3dv(p1.intoVector());
                glVertex3dv(p2.intoVector());
            glEnd();

            if(firstTime){
                firstTime = false;
            }else{
                glLineWidth(5);
                glColor3f(0.0, 0.0, 1.0);
                glBegin(GL_LINES);
                    glVertex3dv(p1.intoVector());
                    glVertex3dv((p1+(n1*(1.0/5.0))).intoVector());
                glEnd();
            }
        }

        glPointSize(1);
        glLineWidth(1);
        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);
    }
    glColor3f(1.0, 1.0, 1.0);
}

void Scene::doRaytracing(){
//    std::cout << "Entrnado no do Raytracing" <<std::endl;

    unsigned int n = camera->getN();
    unsigned int m = camera->getM();
    for(unsigned int i = 0; i < n; i++){
        uchar *line = new uchar[3*m];


        for(unsigned int j = 0; j < m; j++){
//            std::cout << "\nVendo ( " << i << ", " <<j <<  ")" <<std::endl;
            double *color = raytracing(camera->getRay(i, j), depth);
//            std::cout << "FIM" <<std::endl;

//            std::cout << "COR ["<<i << "][" <<j<<"] = (" << color[0] << ", " << color[1] << ", " << color[2] << ")."<<std::endl;
            line[j*3] = (unsigned int)floor(color[0] * 255);//red
            line[j*3 + 1] = (unsigned int)floor(color[1] * 255);//green
            line[j*3 + 2] = (unsigned int)floor(color[2] * 255);//blue
//            line[j*4 + 3] = (unsigned int)255;//alpha

//            std::cout<<"\tCOR = [ " << (unsigned int)line[j*4] << ", " <<(unsigned int)line[j*4 + 1]
//            <<", " << (unsigned int)line[j*4 + 2] <<" ]"<<std::endl;
// ", " << (unsigned int)line[j*4 + 3] <<
            delete color;
        }
        emit setLine(i, line);
    }
}

double *Scene::raytracing(const Ray &_ray, unsigned int _depth){
//    std::cout <<"Começo " << _depth <<std::endl;
    double *color = new double[3];
    color[0] = 0.0;
    color[1] = 0.0;
    color[2] = 0.0;
    if(_depth == 0){
//        color[0] = 0.0;
//        color[1] = 0.0;
//        color[2] = 0.0;
        return color;
    }
    Object *nearestObject = NULL;
    double nearestT = 10000000.0;
    Vector3D nearestPoint;
    Vector3D nearestNormal;
//    std::cout <<"AQUI " << objects.size()<<std::endl;
    for(unsigned int i = 0; i < objects.size(); i++){
//        std::cout <<"i = " << i<<std::endl;
        Object *obj = objects.at(i);
//        std::cout <<"objeto" <<std::endl;
//        std::cout <<"id = " << obj->getId() <<std::endl;
        std::vector<Trio> pointsOfObject = obj->intersection(_ray);
//        std::cout <<"size = " << pointsOfObject.size()<<std::endl;



        for(unsigned int j = 0; j < pointsOfObject.size(); j++){//procurando o mais proximo de todo os objetos existentes
            if(pointsOfObject.at(j).type != OUT){
                double t = (pointsOfObject.at(j).point - _ray.getOrigin()).length_2();
                if(t < nearestT){
                    nearestT = t;
                    nearestObject = obj;
                    nearestPoint = pointsOfObject.at(j).point;
                    nearestNormal = pointsOfObject.at(j).normal;
//                    std::cout <<"A o ponto: " <<std::endl;
//                    nearestPoint.print();
//                    std::cout <<"A normal: " <<std::endl;
//                    nearestNormal.print();
                }

                break;
            }
        }
    }

    if(!nearestObject){
//        color[0] = 0.0;
//        color[1] = 0.0;
//        color[2] = 0.0;
        return color;
    }

//    std::cout <<"\nAchou objeto "<< nearestObject->getId() << " \td = " << depth<<std::endl;
    /**
    //calcular o raio normal
    //calcular o raio refletido relativo ao raio direção negativo
    //mandar o raio para a recursão
    **/
    Vector3D directionToLight = (light->getPosition() - nearestPoint).normalize();

    double ln = nearestNormal.dotProduct(directionToLight);
    Vector3D reflected = (_ray.getDirection() + (nearestNormal * (-2.0 * ln))).normalize();
    Ray reflectionRay(nearestPoint + (reflected*ERROR*10), reflected);


    double *newColor = raytracing(reflectionRay, _depth - 1);


    Ray shadowRay(nearestPoint, directionToLight);
    bool occlusion = false;

    for(unsigned int i = 0; i < objects.size(); i++){
        Object *obj = objects.at(i);
        if(nearestObject == obj) continue;
        std::vector< Trio > pointsOfObject = obj->intersection(shadowRay);

        for(unsigned int j = 0; j < pointsOfObject.size(); j++){//procurando o mais proximo de todo os objetos existentes
            if(pointsOfObject.at(j).type != OUT){
                occlusion = true;
                break;
            }
        }
        if(occlusion) break;
    }
    if(occlusion){
//        std::cout <<"COM OCLUSÂO" <<std::endl;
        const double *globalColor = light->getGlobal();
        const double *materialAmbientColor =  nearestObject->getMaterial().getAmbient();
		for (int j=0; j<3; j++){
			color[j] = ( globalColor[j] * materialAmbientColor[j] );
		}
    }else{
        const double *globalColor = light->getGlobal();
        const double *lightDiffuseColor = light->getMaterial().getDiffuse();
        const double *lightSpecularColor = light->getMaterial().getSpecular();
        const double *materialAmbientColor =  nearestObject->getMaterial().getAmbient();
        const double *materialDiffuseColor =  nearestObject->getMaterial().getDiffuse();
        const double *materialSpecularColor =  nearestObject->getMaterial().getSpecular();
        Vector3D v = (camera->getEye() - nearestPoint).normalize();//a vector pointing to the camera

		for (int j=0; j<3; j++){
			color[j] = ( ( globalColor[j] * materialAmbientColor[j] ) +
			( lightDiffuseColor[j] * materialDiffuseColor[j] * ln) +
			( lightSpecularColor[j] * materialSpecularColor[j] * (v.dotProduct(reflected))) ) +
			newColor[j]*0.3;

		}
    }
    delete newColor;


    if(color[0] > 1.0) color[0] = 1.0;
    if(color[1] > 1.0) color[1] = 1.0;
    if(color[2] > 1.0) color[2] = 1.0;
    if(color[0] < 0.0) color[0] = 0.0;
    if(color[1] < 0.0) color[1] = 0.0;
    if(color[2] < 0.0) color[2] = 0.0;

    return color;
}

void Scene::testRaytracing(const Ray &_r){

    //faz o teste para todas as primitivas e desenha o raio
    listOfPoints.clear();
    if(objects.empty()) return;

    CombinedObject obj(UNION, objects, Material(), -1, true);

    listOfPoints = obj.intersection(_r);

    //inclui um ultimo ponto ao longo do raio, tendo o ultimo ponto base centro
    Vector3D end = listOfPoints.back().point;
    if(listOfPoints.size() == 1) listOfPoints.push_back(Trio(end + (_r.getDirection() * 10), Vector3D(1,0,0), OUT));
    else listOfPoints.push_back(Trio(end + _r.getDirection(), Vector3D(1,0,0), OUT));


}
